import sys
import os
import cStringIO
import imp
from w.i18n.gettext.switcher import useTranslation

class TemplateNotFoundError(Exception):
    def __init__(self, fn):
        super(TemplateNotFoundError, self).__init__(
                'No source or compiled file found for template: %s' % fn )

class Processor(object):
    _instance = None

    def __init__(self):
        if Options.templateRootDirs not in sys.path:
            sys.path.extend(Options.templateRootDirs)

    @classmethod
    def create(cls):
        if not cls._instance:
            cls._instance = cls()
        return cls._instance
        
    def loadTranslation(self, tName, lang):
        if Options.useI18n:
            useTranslation(tName, lang)

class FastPyProcessor(Processor):
    def make(self, name, dataDict):
        dataDict["TSYSTEM"] = Options

        modList = name.split('.')
        try:
            mod = __import__(name, locals(), globals(), fromlist=['*'])
        except ImportError:
            name = '%s.%s'%(Options.defaultImportPath,name)
            mod = __import__(name, locals(), globals(), fromlist=['*'])

        callableName = Options.callableNameFunc(modList[len(modList)-1])

        templateCallable = getattr(mod, callableName)
        template = templateCallable(**Options.varDictParam(dataDict))
        return template

    def process(self, name, dataDict=None, lang=None):
        self.loadTranslation(name, lang)
        return str(self.make(name, dataDict or {}))

#class StaticOnlyProcessor(Processor):
#    def _dumpStatic(self, static, filename):
#        file = open(filename, 'wb')
#        file.write(static);
#        file.close()

#    def _loadStatic(self, filename):
#        file = open(filename, 'rb')
#        res = file.read()
#        file.close()
#        return res

#    def process (self, name, dataDict):
#        dataDict["ctsystem"] = Options
#        statFilename = '%s%s.html' % (Options.templatesRootDir, name.replace(".","/"))
#        return self._loadStatic(statFilename)

class CheetahTemplateProcessor(Processor):
    def __compileFile(self, fn, dataDict):
        #Standart Python2.5 library doesn't contain Cheetah package, so do a local
        #import to avoid unneccessary dependencies
        from Cheetah.CheetahWrapper import CheetahWrapper
        from Cheetah.Template import Template
        
        stdout = sys.stdout # Hide stdout.. quite verbose.
        sys.stdout = cStringIO.StringIO() 
        CheetahWrapper().main(['cheetah-compile', 'compile', fn])
        sys.stdout = stdout
        head, tail = os.path.split(fn)
        
        fp, pathname, description = imp.find_module(tail[:-5], [head])
        try:
            a = imp.load_module(tail[:-5], fp, pathname, description)
        finally:
            if fp: fp.close()
        return a.__dict__[tail[:-5]](searchList=[dataDict])

    def make (self, name, dataDict):
        dataDict["TSYSTEM"] = Options
        
        #allow users to unify paths for compiled and source files through dot
        sourceFn = '%s%s.tmpl'%(Options.templatesRootDir, name.replace(".","/"))
        sourceMTime = os.path.getmtime(sourceFn) if os.path.exists(sourceFn) else 0
        
        template = 0
        pyFn = sourceFn[:-4]+'py'
        pycFn, pyoFn = pyFn +'c', pyFn +'o'

        moduleName = None
        if os.path.exists(pyoFn):
            moduleName = pyoFn
        elif os.path.exists(pycFn):
            moduleName = pycFn
        elif os.path.exists(pyFn):
            moduleName = pyFn

        if not sourceMTime and not moduleName:
            raise TemplateNotFoundError(sourceFn)

        if moduleName and sourceMTime <= os.path.getmtime(moduleName):
            modList = name.split('.')
            mod = __import__(name, locals(), globals(), fromlist['*'])

            templateClass = getattr(mod, modList[len(modList)-1])
            template = templateClass(searchList=[dataDict])
        else:
            template = self.__compileFile(sourceFn, dataDict)

        return template
    
    def process(self, name, dataDict=None, lang=None):
        self.loadTranslation(name, lang)
        return str(self.make(name, dataDict or {}))

class Options(object):
    #each element MUST end with '/'
    templateRootDirs = []
    defaultImportPath = 'w.blocks'
    #callableNameFunc param is a staticmethod that maps module name to the 
    #callable name. If module name == callable name ignore this function
    callableNameFunc = staticmethod(lambda x: x)
    varDictParam = staticmethod(lambda x: x)
    #processor param contain the class of the processor to be used for 
    #template processing. There are two types of processors:
    #    Fast*Processors use fast methods of template loading, but don't implement
    #much check on file existance and never try to rebuild needed form of template.
    #    All other processors try every possible method to load template, usually in 
    #the order: optimized-byte-compiled, byte-compiled, compiled, source
    processorClass = FastPyProcessor
    useI18n = False
